﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MonoStrategy
{
    public delegate void DOnAddBuilding<TSender>(TSender inSender, BaseBuilding inBuilding);
    public delegate void DOnRemoveBuilding<TSender>(TSender inSender, BaseBuilding inBuilding);
    public delegate void DOnAddBuildTask<TSender>(TSender inSender, BuildTask inTask);
    public delegate void DOnRemoveBuildTask<TSender>(TSender inSender, BuildTask inTask);
    public delegate void DOnGradingStep<TSender>(TSender inSender, Movable inGrader, Procedure onCompletion);

    internal class BuildingManager : SynchronizedManager
    {
        private LinkedList<BaseBuilding> m_Buildings = new LinkedList<BaseBuilding>();
        private LinkedList<BuildTask> m_BuildTasks = new LinkedList<BuildTask>();

        internal event DOnGradingStep<BuildingManager> OnGradingStep;
        internal event DOnAddBuilding<BuildingManager> OnAddBuilding;
        internal event DOnAddBuildTask<BuildingManager> OnAddBuildTask;
        internal event DOnRemoveBuilding<BuildingManager> OnRemoveBuilding;
        internal event DOnRemoveBuildTask<BuildingManager> OnRemoveBuildTask;

        internal MovableManager MovMgr { get; private set; }
        internal TerrainDefinition Terrain { get { return MovMgr.Terrain; } }
        internal GameMap Map { get { return ResMgr.Map; } }
        internal ResourceManager ResMgr { get; private set; }
        internal int InitialHouseSpaceCount { get; private set; }

        internal void RaiseGradingStep(Movable inGrader, Procedure onCompletion)
        {
            if (OnGradingStep != null)
                OnGradingStep(this, inGrader, onCompletion);
            else if(onCompletion != null)
                onCompletion();

        }

        internal BuildingManager(MovableManager inMovMgr, ResourceManager inResMgr, int inInitialHouseSpaceCount) : base(inMovMgr)
        {
            if ((inMovMgr == null) || (inResMgr == null))
                throw new ArgumentNullException();

            if ((inInitialHouseSpaceCount < 0) || (inInitialHouseSpaceCount > 1000000))
                throw new ArgumentOutOfRangeException();

            InitialHouseSpaceCount = inInitialHouseSpaceCount;
            MovMgr = inMovMgr;
            ResMgr = inResMgr;
        }

        internal int ComputeHouseSpaceCount()
        {
            int result = InitialHouseSpaceCount;

            foreach (var building in m_Buildings)
            {
                var house = building as SettlerBuilding;

                if (house != null)
                    result += house.Config.SettlerCount;
            }

            return result;
        }

        internal void BeginBuilding(BuildingConfig inConfig, Point inPosition)
        {
            // scan for a proper building spot closely around the specified position
            var checkResult = GridSearch.GridWalkAround(inPosition, Terrain.Size, Terrain.Size, (pos) =>
            {
                if (Math.Abs(pos.X - inPosition.X) > 5)
                    return WalkResult.Abort;

                if (!Terrain.CanBuildingBePlacedAt(pos.X, pos.Y, inConfig))
                    return WalkResult.NotFound;

                inPosition = pos;

                return WalkResult.Success;
            });

            if (checkResult != WalkResult.Success)
                return; // don't raise an exception, since user will notice that building isn't build.

            Terrain.SetWallAt(inPosition, WallValue.Reserved, inConfig.GroundPlane.ToArray());
            Terrain.InitializeWallAt(inPosition, WallValue.Reserved, inConfig.ReservedPlane.ToArray());

            // create instance of building class
            BuildTask task = new BuildTask(this, inConfig.CreateInstance(this, inPosition));

            task.Node = m_BuildTasks.AddLast(task);

            if(OnAddBuildTask != null)
                OnAddBuildTask(this, task);
        }

        internal void EndBuilding(BuildTask inTask)
        {
            BaseBuilding building = inTask.Building;

            RemoveBuildTask(inTask, inKeepPlane:true);

            building.Node = m_Buildings.AddLast(building);
            building.CreateResourceStacks();

            Terrain.SetWallAt(building.Position.ToPoint(), WallValue.Building, building.Config.GroundPlane.ToArray());

            if(OnAddBuilding != null)
                OnAddBuilding(this, building);
        }

        private void RemoveGroundPlane(BaseBuilding inBuilding)
        {
            Terrain.SetWallAt(inBuilding.Position.ToPoint(), WallValue.Free, inBuilding.Config.GroundPlane.ToArray());
            Terrain.SetWallAt(inBuilding.Position.ToPoint(), WallValue.Free, inBuilding.Config.ReservedPlane.ToArray());
        }

        internal void RemoveBuildTask(BuildTask inTask)
        {
            RemoveBuildTask(inTask, inKeepPlane:false);

            // show building crash animation
            VisualUtilities.AnimateAroundCenter(inTask.Building.Center, "Building", "Destroy");

            // build was aborted, drop resources...
            ResMgr.DropResource(inTask.Building.Position.ToPoint(), Resource.Timber, inTask.UsedTimberCount / 2);
            ResMgr.DropResource(inTask.Building.Position.ToPoint(), Resource.Stone, inTask.UsedStoneCount / 2);
        }

        private void RemoveBuildTask(BuildTask inTask, bool inKeepPlane)
        {
            m_BuildTasks.Remove(inTask.Node);
            inTask.Node = null;

            inTask.Dispose();

            if(!inKeepPlane)
                RemoveGroundPlane(inTask.Building);

            if(OnRemoveBuildTask != null)
                OnRemoveBuildTask(this, inTask);
        }

        internal void RemoveBuilding(BaseBuilding inBuilding)
        {
            if (inBuilding.Node == null)
                return;

            m_Buildings.Remove(inBuilding);
            RemoveGroundPlane(inBuilding);

            // show building crash animation
            VisualUtilities.AnimateAroundCenter(inBuilding.Center, "Building", "Destroy");

            // drop resources...
            ResMgr.DropResource(inBuilding.Position.ToPoint(), Resource.Timber, inBuilding.Config.WoodCount / 2);
            ResMgr.DropResource(inBuilding.Position.ToPoint(), Resource.Stone, inBuilding.Config.StoneCount / 2);

            if(OnRemoveBuilding != null)
                OnRemoveBuilding(this, inBuilding);
        }

        internal void RaiseTaskPriority(BuildTask inTask)
        {
            m_BuildTasks.Remove(inTask.Node);
            inTask.Node = m_BuildTasks.AddFirst(inTask);

            foreach (var query in inTask.Queries)
            {
                ResMgr.RaisePriority(query);
            }
        }

        internal void ProcessQueries(Procedure<GenericResourceStack> inHandler)
        {
            LinkedListNode<BuildTask> taskList = m_BuildTasks.First;

            while (taskList != null)
            {
                BuildTask task = taskList.Value;

                foreach (var query in task.Queries)
                {
                    inHandler(query);
                }

                taskList = taskList.Next;
            }
        }

        internal override void ProcessCycle()
        {
            base.ProcessCycle();

            bool isAligned = IsAlignedCycle;

            foreach (var building in m_Buildings)
            {
                if((building.Config.ClassType != typeof(InputOutputBuilding)) && !isAligned)
                    continue;

                building.Update();
            }

            if(isAligned)
            {
                LinkedListNode<BuildTask> taskList = m_BuildTasks.First;

                while(taskList != null)
                {
                    BuildTask task = taskList.Value;
                    LinkedListNode<BuildTask> taskNode = taskList;
                    taskList = taskList.Next;

                    task.Update();

                    if (task.IsCompleted)
                        EndBuilding(taskNode.Value);
                }

                /* After processing, we can now release marked constructors and graders.
                 * If we would have done so during task update, these movables would be
                 * scheduled on the convenience of the tasks following in the queue (LIFO
                 * on average). But this is not the desired behaviour, instead we want to 
                 * schedule them in FIFO order...
                 */
                taskList = m_BuildTasks.First;

                while (taskList != null)
                {
                    BuildTask task = taskList.Value;

                    if (task.AreConstructorsMarkedForRelease)
                        task.ReleaseConstructors();

                    if (task.AreGradersMarkedForRelease)
                        task.ReleaseGraders();

                    taskList = taskList.Next;
                }
            }
        }
    }
}
